{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Copyright (c) MONAI Consortium  \n",
    "Licensed under the Apache License, Version 2.0 (the \"License\");  \n",
    "you may not use this file except in compliance with the License.  \n",
    "You may obtain a copy of the License at  \n",
    "&nbsp;&nbsp;&nbsp;&nbsp;http://www.apache.org/licenses/LICENSE-2.0  \n",
    "Unless required by applicable law or agreed to in writing, software  \n",
    "distributed under the License is distributed on an \"AS IS\" BASIS,  \n",
    "WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  \n",
    "See the License for the specific language governing permissions and  \n",
    "limitations under the License.\n",
    "\n",
    "# Benchmark TensorRT models inference"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "This tutorial shows how to convert a bundle into a TensorRT engine-based TorchScript for better performance and benchmark the latency difference between the original bundle and the TensorRT engine-based TorchScript.\n",
    "\n",
    "The `endoscopic_tool_segmentation` bundle is used as the example in this tutorial. The inference latency before and after the coversion process will be compared. "
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Setup environment\n",
    "\n",
    "The TensorRT conversion is supported since **MONAI 1.2** with **Torch-TensorRT 1.4.0** and **TensorRT 8.5.3**. If a docker container is used, please ensure the MONAI version is >=1.2 or the environment meets the min version requirement."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [],
   "source": [
    "!python -c \"import monai\" || pip install -q \"monai-weekly[nibabel, tqdm]\"\n",
    "!python -c \"import matplotlib\" || pip install -q matplotlib\n",
    "!python -c \"import pandas\" || pip install -q pandas\n",
    "!python -c \"import torch_tensorrt\" || pip install torch_tensorrt\n",
    "%matplotlib inline"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Setup imports"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "import os\n",
    "import tempfile\n",
    "\n",
    "import torch\n",
    "import torch_tensorrt\n",
    "import numpy as np\n",
    "from ignite.engine import Engine\n",
    "import pandas as pd\n",
    "\n",
    "import matplotlib.pyplot as plt\n",
    "import monai\n",
    "from monai.apps import download_and_extract\n",
    "from monai.config import print_config\n",
    "from ignite.engine import Events\n",
    "from collections import OrderedDict\n",
    "from monai.engines import IterationEvents\n",
    "\n",
    "print(f\"Torch-TensorRT version: {torch_tensorrt.__version__}.\")\n",
    "\n",
    "print_config()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Setup data directory\n",
    "\n",
    "You can specify a directory with the `MONAI_DATA_DIRECTORY` environment variable.  \n",
    "This allows you to save results and reuse downloads.  \n",
    "If not specified a temporary directory will be used."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {
    "scrolled": true
   },
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "root dir is: /workspace/data\n"
     ]
    }
   ],
   "source": [
    "directory = os.environ.get(\"MONAI_DATA_DIRECTORY\")\n",
    "if directory is not None:\n",
    "    os.makedirs(directory, exist_ok=True)\n",
    "root_dir = tempfile.mkdtemp() if directory is None else directory\n",
    "print(f\"root dir is: {root_dir}\")"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Download dataset\n",
    "\n",
    "Download and extract the endoscopic_tool_segmentation dataset. The dataset is a subset of the [EndoVis 2017 Robot Instrument Challenge](https://endovissub2017-roboticinstrumentsegmentation.grand-challenge.org/Data/) data. It is used here just for verifying the latency of model inference. Please make sure to read the requirements and usage policies of the challenge data and give credit to the authors of the dataset!"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "endoscopic_tool_dataset.zip: 12.9MB [00:00, 24.0MB/s]                                           "
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "2023-05-05 07:30:06,921 - INFO - Downloaded: /workspace/data/endoscopic_tool_dataset.zip\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "2023-05-05 07:30:06,947 - INFO - Verified 'endoscopic_tool_dataset.zip', md5: f82da47259c0a617202fb54624798a55.\n",
      "2023-05-05 07:30:06,948 - INFO - Writing into directory: /workspace/data.\n"
     ]
    }
   ],
   "source": [
    "resource = \"https://github.com/Project-MONAI/MONAI-extra-test-data/releases/download/0.8.1/endoscopic_tool_dataset.zip\"\n",
    "md5 = \"f82da47259c0a617202fb54624798a55\"\n",
    "\n",
    "compressed_file = os.path.join(root_dir, \"endoscopic_tool_dataset.zip\")\n",
    "data_root = os.path.join(root_dir, \"endoscopic_tool_dataset\")\n",
    "if not os.path.exists(data_root):\n",
    "    download_and_extract(resource, compressed_file, root_dir, md5)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Download the endoscopic_tool_segmentation bundle"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "2023-05-05 07:30:07,024 - INFO - --- input summary of monai.bundle.scripts.download ---\n",
      "2023-05-05 07:30:07,025 - INFO - > name: 'endoscopic_tool_segmentation'\n",
      "2023-05-05 07:30:07,026 - INFO - > bundle_dir: './'\n",
      "2023-05-05 07:30:07,027 - INFO - > source: 'github'\n",
      "2023-05-05 07:30:07,029 - INFO - > remove_prefix: 'monai_'\n",
      "2023-05-05 07:30:07,029 - INFO - > progress: True\n",
      "2023-05-05 07:30:07,030 - INFO - ---\n",
      "\n",
      "\n"
     ]
    },
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "endoscopic_tool_segmentation_v0.5.0.zip: 81.7MB [00:01, 50.0MB/s]                               \n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "2023-05-05 07:30:09,293 - INFO - Downloaded: endoscopic_tool_segmentation_v0.5.0.zip\n",
      "2023-05-05 07:30:09,294 - INFO - Expected md5 is None, skip md5 check for file endoscopic_tool_segmentation_v0.5.0.zip.\n",
      "2023-05-05 07:30:09,295 - INFO - Writing into directory: ..\n"
     ]
    }
   ],
   "source": [
    "bundle_dir = \"./\"\n",
    "bundle_name = \"endoscopic_tool_segmentation\"\n",
    "bundle_path = os.path.join(bundle_dir, bundle_name)\n",
    "if not os.path.exists(bundle_path):\n",
    "    monai.bundle.download(name=bundle_name, bundle_dir=bundle_dir)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Export the TensorRT model with API\n",
    "\n",
    "Export the downloaded bundle model to TensorRT engine-based TorchScript models. It takes a while for the conversion and will generate the `model_trt_fp32.ts` and `model_trt_fp16.ts` into the `models` folder.\n",
    "\n",
    "The command is quite similar to the `ckpt_export` command, except the `precision` and `dynamic_batchsize` parameters. \n",
    "\n",
    "The `precision` parameter specifies the precision of the exported model weight, which currently supports `fp32` and `fp16` as input.\n",
    "\n",
    "The `dynamic_batchsize` parameter is a sequence with three elements to define the batch size range of the input of the model to be converted. Should be a sequence like `[MIN_BATCH, OPT_BATCH, MAX_BATCH]`. After conversion, the batch size of model input should be between `MIN_BATCH` and `MAX_BATCH`. The `OPT_BATCH` is the best performance batch size that the TensorRT tries to fit. It should be the most frequently used input batch size in the application. In this part, we simply assign it to `\"[1, 1, 1]\"`, because the `endoscopic_tool_segmentation` bundle takes a fixed input batch size, when doing the inference."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "%pushd $bundle_path\n",
    "!python -m monai.bundle trt_export network_def --filepath models/model_trt_fp32.ts --ckpt_file models/model.pt --meta_file configs/metadata.json --config_file configs/inference.json --precision fp32 --dynamic_batchsize \"[1, 1, 1]\"\n",
    "!python -m monai.bundle trt_export network_def --filepath models/model_trt_fp16.ts --ckpt_file models/model.pt --meta_file configs/metadata.json --config_file configs/inference.json --precision fp16 --dynamic_batchsize \"[1, 1, 1]\"\n",
    "%popd"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Load the torch model and TensorRT model\n",
    "\n",
    "Define a `monai.bundle.ConfigWorkflow` named `workflow` to fetch elements from the bundle. Load the original bundle model from the `workflow` and load the TensorRT engine-based TorchScript models using `torch.jit.load`. Please note that `torch_tensorrt>=1.4.0` must be imported before loading TensorRT engine-based TorchScript models, since it will provide some runtime context."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "model_weight = os.path.join(bundle_path, \"models\", \"model.pt\")\n",
    "meta_config = os.path.join(bundle_path, \"configs\", \"metadata.json\")\n",
    "inference_config = os.path.join(bundle_path, \"configs\", \"inference.json\")\n",
    "trt_fp32_model_path = os.path.join(bundle_path, \"models\", \"model_trt_fp32.ts\")\n",
    "trt_fp16_model_path = os.path.join(bundle_path, \"models\", \"model_trt_fp16.ts\")\n",
    "\n",
    "workflow = monai.bundle.ConfigWorkflow(\n",
    "    workflow=\"infer\",\n",
    "    config_file=inference_config,\n",
    "    meta_file=meta_config,\n",
    "    logging_file=os.path.join(bundle_path, \"configs\", \"logging.conf\"),\n",
    "    bundle_root=bundle_path,\n",
    ")\n",
    "\n",
    "workflow.initialize()\n",
    "device = workflow.device\n",
    "spatial_shape = (1, 3, 736, 480)\n",
    "model = workflow.network_def\n",
    "model.load_state_dict(torch.load(model_weight, weights_only=True))\n",
    "model.to(device)\n",
    "model.eval()\n",
    "\n",
    "trt_fp32_model = torch.jit.load(trt_fp32_model_path)\n",
    "trt_fp16_model = torch.jit.load(trt_fp16_model_path)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Benchmark the model inference time\n",
    "\n",
    "In this part, the model inference with a random input is benchmarked, i.e. the model computation latency without loading any real data."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Total time for the PyTorch model: 6092.39ms. Average time for the PyTorch model: 12.18ms.\n",
      "Total time for the TensorRT fp32 model: 3146.69ms. Average time for the TensorRT fp32 model: 6.29ms.\n",
      "Total time for the TensorRT fp16 model: 2516.90ms. Average time for the TensorRT fp16 model: 5.03ms.\n"
     ]
    },
    {
     "data": {
      "image/png": "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lZUYN26cWLlypXj//fdFu3bttHm+d+9ebV61adNGfPrpp2Lu3LnC0dFRtG/fXid9kZGRIiAgQAQGBooZM2aIZcuWifDwcGFlZSV++ukn4efnJ+bOnSuWLl0q6tatK9zc3ERmZqZ2/l9++UW0bNlSzJ49W6xatUr897//FR4eHqJ+/foiJydHb/+Fh4eLyMhIsWzZMrFo0SIhhBBz5swRAMSdO3eEEEJcu3ZNBAUFiVatWmmHlUUzb3h4uBgwYID4/PPPxYoVK4QQQsyfP18oFAoxePBg8fnnn4t58+YJLy8vERwcLO7evavd5w0aNBABAQFi/vz54uuvvxbz5s0T7dq1EzExMUIIIaKjowUAsXr1ar31AxBz5szR287o6GghhBBbtmwR9erVE02bNtWW/Z07dwohhPjvf/8rFAqFGDdunPjqq6/EkiVLxNChQ7X5UpbExETh6+srXFxcxNtvvy0++eQT0bJlS6FUKrXHzPXr18W6desEAPHkk09q110WtVotunbtKpRKpZg4caJYtmyZePzxx0WLFi30tl2T50LI43vdunWiadOmol69etr1JCYminXr1gkvLy/RqlUr7fDs7GyhUqlEz549haOjo5g+fbr48ssvxeTJk4W1tbUYMGCAXv6GhYUJb29vMW/ePLFixQpx6tQpkZiYKOrVqycCAwPFe++9J1auXCn+85//CADi008/1c5f2WNh586dolWrVsLLy0ub1i1btpS7HwCIFi1aiMDAQLFo0SKxaNEi4ebmJoKCgsTy5ctFeHi4WLJkiXjnnXeEra2t6N69u878mrLSrl078emnn4qZM2cKBwcHnfIphBA7duwQSqVSNG/eXHzyySfi7bffFm5ubqJZs2aifv36OsusbH0TGRkpIiMjtd9XrVolAIjnnntOfPnll+Kzzz4TY8aMEVOnTi03Dx4mDGQsUGUCmeLiYlFQUKAz7O7du8LX11cnGLhz545eha/xxBNPiIiICJGfn68dplarRadOnUTjxo310tOjRw+hVqu1w1999VVhZWUl0tPThRBCpKenCxcXF9GhQweRl5ens66S83Xs2FF06NBBZ/zmzZsFALF3794yt1kIGcgAEFOmTNFZdt++fYWtra325PvPP/8IAGL9+vU68//55596w+vXry8AiP3792uHJScnCzs7O/H6669rh82ePVsA0Any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",
      "text/plain": [
       "<Figure size 640x480 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "def inference_random_torch_timer(model, input_shape, model_type=\"torch\", warmup_iter=500, active_iter=500):\n",
    "    \"\"\"Do the inference several times and get the latency.\"\"\"\n",
    "\n",
    "    # Do some warmup in case there are extra libraries and models load overhead.\n",
    "    with torch.no_grad():\n",
    "        for _ in range(warmup_iter):\n",
    "            random_input = torch.rand(input_shape, dtype=torch.float32, device=device)\n",
    "            model(random_input)\n",
    "\n",
    "        timeaccumulate = []\n",
    "        for _ in range(active_iter):\n",
    "            starter, ender = torch.cuda.Event(enable_timing=True), torch.cuda.Event(enable_timing=True)\n",
    "            torch.cuda.synchronize()\n",
    "            starter.record()\n",
    "            model(random_input)\n",
    "            ender.record()\n",
    "            torch.cuda.synchronize()\n",
    "            time_cur = starter.elapsed_time(ender)\n",
    "            timeaccumulate.append(time_cur)\n",
    "        total_time = sum(timeaccumulate)\n",
    "        average_time = total_time / (len(timeaccumulate) + 1e-12)\n",
    "    print(\n",
    "        f\"Total time for the {model_type} model: {total_time:.2f}ms.\",\n",
    "        f\"Average time for the {model_type} model: {average_time:.2f}ms.\",\n",
    "    )\n",
    "    return timeaccumulate\n",
    "\n",
    "\n",
    "# Only test the fp32 precision torch model\n",
    "torch.backends.cuda.matmul.allow_tf32 = False\n",
    "torch.backends.cudnn.allow_tf32 = False\n",
    "\n",
    "torch_time_list = inference_random_torch_timer(model, spatial_shape, model_type=\"PyTorch\")\n",
    "trt32_time_list = inference_random_torch_timer(trt_fp32_model, spatial_shape, model_type=\"TensorRT fp32\")\n",
    "trt16_time_list = inference_random_torch_timer(trt_fp16_model, spatial_shape, model_type=\"TensorRT fp16\")\n",
    "\n",
    "plt.title(\"Latency benchmark results of different models.\")\n",
    "plt.xlabel(\"Iteration\")\n",
    "plt.ylabel(\"Latency (ms)\")\n",
    "plt.plot(torch_time_list, color=\"red\", label=\"PyTorch model\")\n",
    "plt.plot(trt32_time_list, color=\"green\", label=\"TensorRT fp32 model\")\n",
    "plt.plot(trt16_time_list, color=\"blue\", label=\"TensorRT fp16 model\")\n",
    "plt.legend()\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Benchmark the end-to-end bundle run\n",
    "\n",
    "In this part, the `workflow` defined previously is used to do the end-to-end inference. A `TimerHandler` is defined to benchmark every part of the inference process."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Define the TimerHandler\n",
    "\n",
    "Define a `CUDATimer` to time the latency on GPU and a `TimerHandler` to attach this timer to every part of an end-to-end inference."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [],
   "source": [
    "class CUDATimer:\n",
    "    def __init__(self, type_str) -> None:\n",
    "        self.time_list = []\n",
    "        self.type_str = type_str\n",
    "\n",
    "    def start(self) -> None:\n",
    "        self.starter = torch.cuda.Event(enable_timing=True)\n",
    "        self.ender = torch.cuda.Event(enable_timing=True)\n",
    "        torch.cuda.synchronize()\n",
    "        self.starter.record()\n",
    "\n",
    "    def end(self) -> None:\n",
    "        self.ender.record()\n",
    "        torch.cuda.synchronize()\n",
    "        self.time_list.append(self.starter.elapsed_time(self.ender))\n",
    "\n",
    "    def get_max(self) -> float:\n",
    "        return max(self.time_list)\n",
    "\n",
    "    def get_min(self) -> float:\n",
    "        return min(self.time_list)\n",
    "\n",
    "    def get_mean(self) -> float:\n",
    "        np_time = np.array(self.time_list)\n",
    "        return np.mean(np_time)\n",
    "\n",
    "    def get_std(self) -> float:\n",
    "        np_time = np.array(self.time_list)\n",
    "        return np.std(np_time)\n",
    "\n",
    "    def get_sum(self) -> float:\n",
    "        np_time = np.array(self.time_list)\n",
    "        return np.sum(np_time)\n",
    "\n",
    "    def get_results_dict(self) -> OrderedDict:\n",
    "        out_list = [\n",
    "            (\"total\", self.get_sum()),\n",
    "            (\"min\", self.get_min()),\n",
    "            (\"max\", self.get_max()),\n",
    "            (\"mean\", self.get_mean()),\n",
    "            (\"std\", self.get_std()),\n",
    "        ]\n",
    "        return OrderedDict(out_list)\n",
    "\n",
    "\n",
    "class TimerHandler:\n",
    "    def __init__(self) -> None:\n",
    "        self.run_timer = CUDATimer(\"RUN\")\n",
    "        self.epoch_timer = CUDATimer(\"EPOCH\")\n",
    "        self.iteration_timer = CUDATimer(\"ITERATION\")\n",
    "        self.net_forward_timer = CUDATimer(\"FORWARD\")\n",
    "        self.get_batch_timer = CUDATimer(\"PREPARE_BATCH\")\n",
    "        self.post_process_timer = CUDATimer(\"POST_PROCESS\")\n",
    "        self.timer_list = [\n",
    "            self.run_timer,\n",
    "            self.epoch_timer,\n",
    "            self.iteration_timer,\n",
    "            self.net_forward_timer,\n",
    "            self.get_batch_timer,\n",
    "            self.post_process_timer,\n",
    "        ]\n",
    "\n",
    "    def attach(self, engine: Engine) -> None:\n",
    "        engine.add_event_handler(Events.STARTED, self.started, timer=self.run_timer)\n",
    "        engine.add_event_handler(Events.EPOCH_STARTED, self.started, timer=self.epoch_timer)\n",
    "        engine.add_event_handler(Events.ITERATION_STARTED, self.started, timer=self.iteration_timer)\n",
    "        engine.add_event_handler(Events.GET_BATCH_STARTED, self.started, timer=self.get_batch_timer)\n",
    "        engine.add_event_handler(Events.GET_BATCH_COMPLETED, self.completed, timer=self.get_batch_timer)\n",
    "        engine.add_event_handler(Events.GET_BATCH_COMPLETED, self.started, timer=self.net_forward_timer)\n",
    "        engine.add_event_handler(IterationEvents.FORWARD_COMPLETED, self.completed, timer=self.net_forward_timer)\n",
    "        engine.add_event_handler(IterationEvents.FORWARD_COMPLETED, self.started, timer=self.post_process_timer)\n",
    "        engine.add_event_handler(Events.ITERATION_COMPLETED, self.completed, timer=self.post_process_timer)\n",
    "        engine.add_event_handler(Events.ITERATION_COMPLETED, self.completed, timer=self.iteration_timer)\n",
    "        engine.add_event_handler(Events.EPOCH_COMPLETED, self.completed, timer=self.epoch_timer)\n",
    "        engine.add_event_handler(Events.COMPLETED, self.completed, timer=self.run_timer)\n",
    "\n",
    "    def started(self, engine: Engine, timer: CUDATimer) -> None:\n",
    "        timer.start()\n",
    "\n",
    "    def completed(self, engine: Engine, timer: CUDATimer) -> None:\n",
    "        timer.end()\n",
    "\n",
    "    def print_results(self) -> None:\n",
    "        index = [x.type_str for x in self.timer_list]\n",
    "        column_title = list(self.timer_list[0].get_results_dict().keys())\n",
    "        column_title = [x + \"/ms\" for x in column_title]\n",
    "        latency_list = [x for timer in self.timer_list for x in timer.get_results_dict().values()]\n",
    "        latency_array = np.array(latency_list)\n",
    "        latency_array = np.reshape(latency_array, (len(index), len(column_title)))\n",
    "        df = pd.DataFrame(latency_array, index=index, columns=column_title)\n",
    "        return df"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Benchmark the MONAI bundle\n",
    "\n",
    "In this part, the `workflow` is updated to do the inference on the downloaded `endoscopic_tool_dataset`. It runs several iterations to benchmark the latency."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "workflow.dataset_dir = data_root\n",
    "workflow.bundle_root = bundle_path\n",
    "\n",
    "workflow.initialize()\n",
    "torch_timer = TimerHandler()\n",
    "torch_timer.attach(workflow.evaluator)\n",
    "workflow.run()\n",
    "workflow.finalize()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 11,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>total/ms</th>\n",
       "      <th>min/ms</th>\n",
       "      <th>max/ms</th>\n",
       "      <th>mean/ms</th>\n",
       "      <th>std/ms</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>RUN</th>\n",
       "      <td>7076.975586</td>\n",
       "      <td>7076.975586</td>\n",
       "      <td>7076.975586</td>\n",
       "      <td>7076.975586</td>\n",
       "      <td>0.000000</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>EPOCH</th>\n",
       "      <td>7076.116211</td>\n",
       "      <td>7076.116211</td>\n",
       "      <td>7076.116211</td>\n",
       "      <td>7076.116211</td>\n",
       "      <td>0.000000</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>ITERATION</th>\n",
       "      <td>5810.647820</td>\n",
       "      <td>37.419552</td>\n",
       "      <td>53.915840</td>\n",
       "      <td>38.737652</td>\n",
       "      <td>1.834347</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>FORWARD</th>\n",
       "      <td>4244.921793</td>\n",
       "      <td>27.471424</td>\n",
       "      <td>34.854145</td>\n",
       "      <td>28.299479</td>\n",
       "      <td>1.276970</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>PREPARE_BATCH</th>\n",
       "      <td>733.171203</td>\n",
       "      <td>3.439424</td>\n",
       "      <td>45.551266</td>\n",
       "      <td>4.887808</td>\n",
       "      <td>3.683113</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>POST_PROCESS</th>\n",
       "      <td>1556.357985</td>\n",
       "      <td>9.772064</td>\n",
       "      <td>19.000769</td>\n",
       "      <td>10.375720</td>\n",
       "      <td>0.869200</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "                  total/ms       min/ms       max/ms      mean/ms    std/ms\n",
       "RUN            7076.975586  7076.975586  7076.975586  7076.975586  0.000000\n",
       "EPOCH          7076.116211  7076.116211  7076.116211  7076.116211  0.000000\n",
       "ITERATION      5810.647820    37.419552    53.915840    38.737652  1.834347\n",
       "FORWARD        4244.921793    27.471424    34.854145    28.299479  1.276970\n",
       "PREPARE_BATCH   733.171203     3.439424    45.551266     4.887808  3.683113\n",
       "POST_PROCESS   1556.357985     9.772064    19.000769    10.375720  0.869200"
      ]
     },
     "execution_count": 11,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "torch_timer.print_results()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "### Benchmark the TensorRT fp32 and fp16 models\n",
    "In this part, the `trt_fp32_model` and `trt_fp16_model` are loaded to the `workflow`. The updated `workflow` runs the same iterations as before to benchmark the latency difference. Since the `trt_fp32_model` and `trt_fp16_model` cannot be loaded through the `CheckpointLoader` and don't have `amp` mode, disable the `CheckpointLoader` in the `initialize` of the `workflow` and the `amp` parameter in the `evaluator` of the `workflow` needs to be set to `False`.\n",
    "\n",
    "The `POST_PROCESS` and `PREPARE_BATCH` stages require a considerable amount of time. Although the model forward time is much improved, there is still room for acceleration in reducing the end-to-end latency on this particular MONAI bundle."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "workflow.initialize()\n",
    "workflow.add_property(\"load_pretrain\", True, \"load_pretrain\")\n",
    "workflow.load_pretrain = False\n",
    "workflow.network_def = trt_fp32_model\n",
    "\n",
    "workflow.initialize()\n",
    "trt32_timer = TimerHandler()\n",
    "trt32_timer.attach(workflow.evaluator)\n",
    "workflow.evaluator.amp = False\n",
    "workflow.run()\n",
    "workflow.finalize()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>total/ms</th>\n",
       "      <th>min/ms</th>\n",
       "      <th>max/ms</th>\n",
       "      <th>mean/ms</th>\n",
       "      <th>std/ms</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>RUN</th>\n",
       "      <td>3976.694092</td>\n",
       "      <td>3976.694092</td>\n",
       "      <td>3976.694092</td>\n",
       "      <td>3976.694092</td>\n",
       "      <td>0.000000</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>EPOCH</th>\n",
       "      <td>3975.839111</td>\n",
       "      <td>3975.839111</td>\n",
       "      <td>3975.839111</td>\n",
       "      <td>3975.839111</td>\n",
       "      <td>0.000000</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>ITERATION</th>\n",
       "      <td>2692.477089</td>\n",
       "      <td>17.192833</td>\n",
       "      <td>22.593151</td>\n",
       "      <td>17.949847</td>\n",
       "      <td>0.802514</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>FORWARD</th>\n",
       "      <td>1114.370371</td>\n",
       "      <td>7.328000</td>\n",
       "      <td>8.935040</td>\n",
       "      <td>7.429136</td>\n",
       "      <td>0.176884</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>PREPARE_BATCH</th>\n",
       "      <td>702.381890</td>\n",
       "      <td>0.162880</td>\n",
       "      <td>48.027744</td>\n",
       "      <td>4.682546</td>\n",
       "      <td>4.087289</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>POST_PROCESS</th>\n",
       "      <td>1570.041856</td>\n",
       "      <td>9.778080</td>\n",
       "      <td>15.112832</td>\n",
       "      <td>10.466946</td>\n",
       "      <td>0.718460</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "                  total/ms       min/ms       max/ms      mean/ms    std/ms\n",
       "RUN            3976.694092  3976.694092  3976.694092  3976.694092  0.000000\n",
       "EPOCH          3975.839111  3975.839111  3975.839111  3975.839111  0.000000\n",
       "ITERATION      2692.477089    17.192833    22.593151    17.949847  0.802514\n",
       "FORWARD        1114.370371     7.328000     8.935040     7.429136  0.176884\n",
       "PREPARE_BATCH   702.381890     0.162880    48.027744     4.682546  4.087289\n",
       "POST_PROCESS   1570.041856     9.778080    15.112832    10.466946  0.718460"
      ]
     },
     "execution_count": 13,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "trt32_timer.print_results()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "workflow.network_def = trt_fp16_model\n",
    "\n",
    "workflow.initialize()\n",
    "trt16_timer = TimerHandler()\n",
    "trt16_timer.attach(workflow.evaluator)\n",
    "workflow.evaluator.amp = False\n",
    "workflow.run()\n",
    "workflow.finalize()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 15,
   "metadata": {
    "tags": []
   },
   "outputs": [
    {
     "data": {
      "text/html": [
       "<div>\n",
       "<style scoped>\n",
       "    .dataframe tbody tr th:only-of-type {\n",
       "        vertical-align: middle;\n",
       "    }\n",
       "\n",
       "    .dataframe tbody tr th {\n",
       "        vertical-align: top;\n",
       "    }\n",
       "\n",
       "    .dataframe thead th {\n",
       "        text-align: right;\n",
       "    }\n",
       "</style>\n",
       "<table border=\"1\" class=\"dataframe\">\n",
       "  <thead>\n",
       "    <tr style=\"text-align: right;\">\n",
       "      <th></th>\n",
       "      <th>total/ms</th>\n",
       "      <th>min/ms</th>\n",
       "      <th>max/ms</th>\n",
       "      <th>mean/ms</th>\n",
       "      <th>std/ms</th>\n",
       "    </tr>\n",
       "  </thead>\n",
       "  <tbody>\n",
       "    <tr>\n",
       "      <th>RUN</th>\n",
       "      <td>3920.113037</td>\n",
       "      <td>3920.113037</td>\n",
       "      <td>3920.113037</td>\n",
       "      <td>3920.113037</td>\n",
       "      <td>0.000000</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>EPOCH</th>\n",
       "      <td>3919.248291</td>\n",
       "      <td>3919.248291</td>\n",
       "      <td>3919.248291</td>\n",
       "      <td>3919.248291</td>\n",
       "      <td>0.000000</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>ITERATION</th>\n",
       "      <td>2533.983199</td>\n",
       "      <td>15.867840</td>\n",
       "      <td>24.442944</td>\n",
       "      <td>16.893221</td>\n",
       "      <td>1.204370</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>FORWARD</th>\n",
       "      <td>939.148706</td>\n",
       "      <td>6.074368</td>\n",
       "      <td>8.269696</td>\n",
       "      <td>6.260991</td>\n",
       "      <td>0.278483</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>PREPARE_BATCH</th>\n",
       "      <td>725.743360</td>\n",
       "      <td>0.170112</td>\n",
       "      <td>41.243263</td>\n",
       "      <td>4.838289</td>\n",
       "      <td>3.670066</td>\n",
       "    </tr>\n",
       "    <tr>\n",
       "      <th>POST_PROCESS</th>\n",
       "      <td>1586.734653</td>\n",
       "      <td>9.739744</td>\n",
       "      <td>18.202560</td>\n",
       "      <td>10.578231</td>\n",
       "      <td>1.128791</td>\n",
       "    </tr>\n",
       "  </tbody>\n",
       "</table>\n",
       "</div>"
      ],
      "text/plain": [
       "                  total/ms       min/ms       max/ms      mean/ms    std/ms\n",
       "RUN            3920.113037  3920.113037  3920.113037  3920.113037  0.000000\n",
       "EPOCH          3919.248291  3919.248291  3919.248291  3919.248291  0.000000\n",
       "ITERATION      2533.983199    15.867840    24.442944    16.893221  1.204370\n",
       "FORWARD         939.148706     6.074368     8.269696     6.260991  0.278483\n",
       "PREPARE_BATCH   725.743360     0.170112    41.243263     4.838289  3.670066\n",
       "POST_PROCESS   1586.734653     9.739744    18.202560    10.578231  1.128791"
      ]
     },
     "execution_count": 15,
     "metadata": {},
     "output_type": "execute_result"
    }
   ],
   "source": [
    "trt16_timer.print_results()"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.8.10"
  },
  "vscode": {
   "interpreter": {
    "hash": "31f2aee4e71d21fbe5cf8b01ff0e069b9275f58929596ceb00d14d90e3e16cd6"
   }
  }
 },
 "nbformat": 4,
 "nbformat_minor": 4
}
